﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using WcfActivePassiveWorkerRole;
using Work;
using WcfMasterSlaveElectionService;

namespace MasterSlaveWorker
{
    public class WorkerRole : ActivePassiveWorkerRole
    {
        private LogClient _logger;
        /// <summary>
        /// Should always be false. If set to true (use method Set()), the Run method will end
        /// </summary>
        private ManualResetEvent _recycleEvent;
        /// <summary>
        /// While false, keeps the masterthread alive. If set to true, the master thread will end
        /// </summary>
        private ManualResetEvent _masterRecycleEvent;
        /// <summary>
        /// Master func thread: the actual work of the master
        /// </summary>
        private Thread _masterFunc;

        #region Overrides of ActivePassiveWorkerRole

        protected override void ChangeToSlaveMode()
        {
            try
            {
                string msg = string.Format("{0} is starting as slave", MasterElector.GetHostId);
                Logger(msg);

                // Obey, you slave!
                // Always the first node will be the master (1master-nslave implementation)                
                _masterRecycleEvent.Set();

                msg = string.Format("{0} started as slave", MasterElector.GetHostId);
                Logger(msg);
            }
            catch (Exception ex)
            {
                LogError("Error in startAsSlave: {0}", ex.Message);
                RoleEnvironment.RequestRecycle();
            }
        }

        protected override void ChangeToMasterMode()
        {
            try
            {
                string msg = String.Format("{0} is starting as master", MasterElector.GetHostId);
                Logger(msg);

                // Do your master thingy...
                // If you can't start as master, then Recycle the role:
                //RoleEnvironment.RequestRecycle();
                startFunc();
                Logger("{0} started as master", MasterElector.GetHostId);
            }
            catch (Exception ex)
            {
                LogError("Error in startAsMaster: {0}", ex.Message);
                RoleEnvironment.RequestRecycle();
            }
        }

        #endregion

        /// <summary>
        /// The RequestRecycle does not work in the Compute Emulator. For testing, use the Fakerecycle
        /// </summary>
        void FakeRecycle()
        {
            _masterRecycleEvent.Set();
            count = 0;
            Logger("workerrole shutdown happened: {0}", RoleEnvironment.CurrentRoleInstance.Id);

            MasterElector.StopListen();
            Logger("Service host stopped (fake)\n");

            Thread.Sleep(30000);
            InstantiateMasterElectionService(_logger);
        }

        void RoleEnvironment_Stopping(object sender, RoleEnvironmentStoppingEventArgs e)
        {
            Logger("Received StoppingEvent: {0}", RoleEnvironment.CurrentRoleInstance.Id);
            _masterRecycleEvent.Set();
            count = 0;
            Logger("workerrole shutdown happened: {0}", RoleEnvironment.CurrentRoleInstance.Id);

            MasterElector.StopListen();
            Logger("Service host stopped (for real)\n");
        }

        public override void Run()
        {
            // This is a sample worker implementation.
            try
            {
                
                int retries = 0;
                while (!_recycleEvent.WaitOne(TimeSpan.FromSeconds(double.Parse(RoleEnvironment.GetConfigurationSettingValue("RecycleThreadSleepTime")))))
                {
                    // Keep checking if the watchdig is running. In this sample we allow
                    // the watchdog to be not alive for several cycles. When the watchdog has not been 
                    // alive for 5 cycles we request for a recycle
                    if (!MasterElector.IsWatchdogAlive())
                    {
                        retries++;
                        Logger("Master watchdog thread is not alive. ");
                    }
                    else
                    {
                        retries = 0;
                        Logger("Health Monitor: Master watchdog thread is alive");
                    }
                    if (retries > 5)
                    {
                        Logger("Master watchdog thread is not responding. Recycling role...");
                        RoleEnvironment.RequestRecycle();
                    }
                }
            }
            catch (Exception ex)
            {
                LogError("Error in start method of worker role: {0}", ex.Message);
            }
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            //ServicePointManager.DefaultConnectionLimit = 12;

            #region Setup diagnostics
            LocalResource localResource = RoleEnvironment.GetLocalResource("CustomLog");
            Trace.Listeners.Add(new TextWriterTraceListener(localResource.RootPath + RoleEnvironment.CurrentRoleInstance.Id + ".log"));

            DiagnosticMonitorConfiguration diagConfig = DiagnosticMonitor.GetDefaultInitialConfiguration();
            SetupDiagConfiguration(diagConfig);
            DiagnosticMonitor.Start("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString", diagConfig);
            #endregion
            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
            #region Setup CloudStorageAccount Configuration Setting Publisher

            // This code sets up a handler to update CloudStorageAccount instances when their corresponding
            // configuration settings change in the service configuration file.
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // Provide the configSetter with the initial value
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

                RoleEnvironment.Changed += (sender, arg) =>
                {
                    Logger("Received ChangedEvent: {0}", RoleEnvironment.CurrentRoleInstance.Id);
                    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        // The corresponding configuration setting has changed, propagate the value
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            // In this case, the change to the storage account credentials in the
                            // service configuration is significant enough that the role needs to be
                            // recycled in order to use the latest settings. (for example, the 
                            // endpoint has changed)
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });
            #endregion

         
            var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

            // dynamically create the tables
            CloudTableClient.CreateTablesFromModel(typeof(WorkDataServiceContext), account.TableEndpoint.AbsoluteUri, account.Credentials);

            _masterRecycleEvent = new ManualResetEvent(false);
            _recycleEvent = new ManualResetEvent(false);

            // Subscribe to the Stopping Event, to assign a new master.
            RoleEnvironment.Stopping += new EventHandler<RoleEnvironmentStoppingEventArgs>(RoleEnvironment_Stopping);

            // Instantiate a logger to be passed to the WCF Master Slave Election Service
            _logger = new LogClient();

            // Instantiate the WCF Master Slave Election
            InstantiateMasterElectionService(_logger);

            return base.OnStart();
        }

        int count = 0;

        /// <summary>
        /// SAMPLE: The function the master has to perform
        /// </summary>
        private void startFunc()
        {
            _masterRecycleEvent.Reset();
            count = 0;

            _masterFunc = new Thread(delegate()
            {
                Logger("Starting Master func thread");
                while (!_masterRecycleEvent.WaitOne(TimeSpan.FromSeconds(30)))
                {
                    var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
                    var context = new WorkDataServiceContext(account.TableEndpoint.ToString(), account.Credentials);

                    //context.AddMessage(RoleEnvironment.CurrentRoleInstance.Id);
                    Trace.TraceInformation("Do by: {0}", RoleEnvironment.CurrentRoleInstance.Id);

                    if (count >= 10)
                    {
                        RoleEnvironment.RequestRecycle();
                        //FakeRecycle();
                    }
                    count++;
                }
                IAmMaster = false;
                Logger("Ending Master func thread");
            });

            _masterFunc.Start();
        }


        #region Plumbing
        /// <summary>
        /// Log message in cloud
        /// </summary>
        /// <param name="fmt"></param>
        /// <param name="args"></param>
        private void Logger(string fmt, params object[] args)
        {
            _logger.LogMessage(fmt, args);
        }

        /// <summary>
        /// Log error in cloud.
        /// </summary>
        /// <param name="fmt"></param>
        /// <param name="args"></param>
        protected void LogError(string fmt, params object[] args)
        {
            _logger.LogMessage(fmt, args);
        }

        private void SetupDiagConfiguration(DiagnosticMonitorConfiguration diagConfig)
        {
            // Azure application logs  
            diagConfig.Logs.ScheduledTransferLogLevelFilter = LogLevel.Error;
            diagConfig.Logs.ScheduledTransferPeriod = TimeSpan.FromDays(1);

            LocalResource localResource = RoleEnvironment.GetLocalResource("CustomLog");

            diagConfig.Directories.DataSources.Clear();
            DirectoryConfiguration directoryConfiguration = new DirectoryConfiguration();
            directoryConfiguration.Container = "customlog-container";
            directoryConfiguration.DirectoryQuotaInMB = localResource.MaximumSizeInMegabytes;
            directoryConfiguration.Path = localResource.RootPath;
            diagConfig.Directories.DataSources.Add(directoryConfiguration);
            diagConfig.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(1);

            diagConfig.WindowsEventLog.DataSources.Add("Application!*");
            diagConfig.PerformanceCounters.DataSources.Clear();
            diagConfig.Logs.ScheduledTransferLogLevelFilter = LogLevel.Verbose;
            diagConfig.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1);

        }  
        #endregion
    }
}
